﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#include "CLDevice.h"

namespace UX_STL
{
namespace CLShell
{

#ifdef _UXS_STL_CONFIGURATION_H
	CLDevice *	_s_pCLDevice	= null;
#endif

#ifdef PLATFORM_WINDOWS
	static const int	CL_PLATFORM_DEVICE_TYPE = CL_WGL_HDC_KHR;
#endif	// PLATFORM_WINDOWS

#if defined(PLATFORM_LINUX) or defined(PLATFORM_MACOSX)
	static const int	CL_PLATFORM_DEVICE_TYPE = CL_GLX_DISPLAY_KHR;
#endif	// PLATFORM_LINUX


/*
=================================================
	конструктор
=================================================
*/
	CLDevice::CLDevice():
		_pPlatform(null), _pDevice(null),
		_pContext(null), _bSupportNative(false),
		_bSharedWithGL(false), _bSharedWithDX(false)
	{

		// init global variables
#	ifdef _UXS_STL_CONFIGURATION_H
		ASSERT( _s_pCLDevice == null );
		_s_pCLDevice = this;
#	endif

	}
	
/*
=================================================
	деструктор
=================================================
*/
	CLDevice::~CLDevice()
	{
		Destroy();

#	ifdef _UXS_STL_CONFIGURATION_H
		ASSERT( _s_pCLDevice == this );
		_s_pCLDevice = null;
#	endif
	}
	
/*
=================================================
	инициализация OpenCL контекста
=================================================
*/
	bool CLDevice::Init(uni_c_string sDeviceName, cl_device::type eDevice)
	{
		Destroy();

		CHECK_ERR( _ChooseDevice( sDeviceName, eDevice ) );
	
		// Create Context //
		cl_context_properties props[] =
		{
			CL_CONTEXT_PLATFORM,	(cl_context_properties)_pPlatform,
			0,	0
		};

		CHECK_ERR( _CreateContext( props ) );

		_OnInit();
		return true;
	}
	
/*
=================================================
	инициализация OpenCL контекста
=================================================
*/
	bool CLDevice::Init(uni_c_string sDeviceName, cl_device::type eDevice, void *pGLContext, void *pDeviceContext)
	{
		Destroy();

		CHECK_ERR( pGLContext != null and pDeviceContext != null );
		CHECK_ERR( _ChooseDevice( sDeviceName, eDevice ) );
	
		// Create Context //
		cl_context_properties props[] =
		{
			CL_CONTEXT_PLATFORM,	 (cl_context_properties)_pPlatform,
			CL_GL_CONTEXT_KHR,		 (cl_context_properties)pGLContext,			// RC
			CL_PLATFORM_DEVICE_TYPE, (cl_context_properties)pDeviceContext,		// DC
			0,	0
		};

		CHECK_ERR( _CreateContext( props ) );

		_OnInit();

		_bSharedWithGL = true;
		return true;
	}
	
/*
=================================================
	инициализация OpenCL контекста
=================================================
*/
DIRECTX_ONLY(
	bool CLDevice::Init(uni_c_string sDeviceName, cl_device::type eDevice, void *pDXDevice)
	{
		Destroy();

		CHECK_ERR( pDXDevice != null );
		CHECK_ERR( _ChooseDevice( sDeviceName, eDevice ) );

		// TODO: clGetDeviceIDsFromD3D10KHR
	
		// Create Context //
		cl_context_properties props[] =
		{
			CL_CONTEXT_PLATFORM,	(cl_context_properties)_pPlatform,
			0,	0
		};

		CHECK_ERR( _CreateContext( props ) );

		_OnInit();
		return true;
	}
)
	
/*
=================================================
	инициализация
=================================================
*/
	bool CLDevice::Init(cl_platform_id pPlatform, cl_device_id pDevice, cl_context pContext)
	{
		CHECK_ERR( pPlatform != null and pDevice != null and pContext != null );

		Destroy();

		_pPlatform = pPlatform;
		_pDevice   = pDevice;
		_pContext  = pContext;

		_OnInit();
		return true;
	}
	
/*
=================================================
	инициализация
=================================================
*/
	void CLDevice::_OnInit()
	{
		cl_int	i_cl_err = 0;

		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(cl_ulong), &_uTotalMemory, null ) );

		// check is supported native kernel //
		cl_device_exec_capabilities		i_cap = 0;
		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_EXECUTION_CAPABILITIES, sizeof(i_cap), &i_cap, null ) );
		_bSupportNative = EnumCmp( i_cap, CL_EXEC_NATIVE_KERNEL );
	}
	
/*
=================================================
	выбрать девайс
=================================================
*/
	bool CLDevice::_ChooseDevice(uni_c_string sDeviceName, cl_device::type eDevice)
	{
		char	res = CL_Init();

		if ( res != 0 )
		{
			LOG( "Error(s) while initialize OpenCL.", e_log_msg::WARNING_NL_4 );

			const char	*p_error = CL_GetError();

			while ( p_error != null )
			{
				LOG( p_error, e_log_msg::ERROR_NL_4 );
				p_error = CL_GetError();
			}

			CL_ClearErrors();

			if ( res == 2 )
				RETURN_ERR( "Critical error while init OpenCL" );
		}

		cl_int					i_cl_err	= 0;
		cl_uint					u_numb_pl	= 0;
		array<cl_platform_id>	a_platforms;

		// Choose Platform //
		CL_CALL_R( i_cl_err = clGetPlatformIDs( 0, null, &u_numb_pl ) );
		CHECK_ERR( u_numb_pl != 0 );

		a_platforms.Resize( u_numb_pl );

		CL_CALL( i_cl_err = clGetPlatformIDs( u_numb_pl, a_platforms.ptr(), null ) );
		_pPlatform = a_platforms[0];


		// Choose Device //
		cl_uint					u_numb_dev	= 0;
		array<cl_device_id>		a_devices;
	
		CL_CALL_R( i_cl_err = clGetDeviceIDs( _pPlatform, CL_DEVICE_TYPE_ALL, 0, null, &u_numb_dev ) );
		CHECK_ERR( u_numb_dev != 0 );
		
		a_devices.Resize( u_numb_dev );

		CL_CALL_R( i_cl_err = clGetDeviceIDs( _pPlatform, CL_DEVICE_TYPE_ALL, u_numb_dev, a_devices.ptr(), null ) );
	
		if ( eDevice == cl_device::UNKNOWN )
			eDevice = cl_device::ALL;


		// search device for name and type
		if ( !sDeviceName.Empty() )
		{
			const uint		u_max_size = 512;	
			string			a_info;
			usize			u_pos = 0;
			cl_device::type	e_dev_type = cl_device::UNKNOWN;
			a_info.Reserve( u_max_size );

			for (uint i = 0; i < u_numb_dev; ++i)
			{
				bool	b_res = true;
				
				CL_CALL_B( i_cl_err = clGetDeviceInfo( a_devices[i], CL_DEVICE_TYPE, sizeof(e_dev_type), &e_dev_type, null ), b_res );

				if ( !b_res or EnumNoCmp( e_dev_type, eDevice ) )
					continue;
				
				a_info[0] = '\0';
				CL_CALL_B( i_cl_err = clGetDeviceInfo( a_devices[i], CL_DEVICE_NAME, u_max_size, a_info.ptr(), null ), b_res );
				
				if ( b_res and
					uni_c_string(a_info.ptr()).FindI( sDeviceName, u_pos, 0 ) )
				{
					_pDevice = a_devices[i];
					break;
				}
				
				a_info[0] = '\0';
				CL_CALL_B( i_cl_err = clGetDeviceInfo( a_devices[i], CL_DEVICE_VENDOR, u_max_size, a_info.ptr(), null ), b_res );

				if ( b_res and
					uni_c_string(a_info.ptr()).FindI( sDeviceName, u_pos, 0 ) )
				{
					_pDevice = a_devices[i];
					break;
				}
			}
		}


		// search device for type
		if ( _pDevice == null )
		{
			cl_device::type	e_dev_type = cl_device::UNKNOWN;
			usize			u_gpu_dev = usize(-1),
							u_cpu_dev = usize(-1);

			for (uint i = 0; i < u_numb_dev; ++i)
			{
				bool	b_res = true;

				CL_CALL_B( i_cl_err = clGetDeviceInfo( a_devices[i], CL_DEVICE_TYPE, sizeof(e_dev_type), &e_dev_type, null ), b_res );

				if ( !b_res )
					continue;

				if ( EnumCmp( e_dev_type, eDevice ) ) {
					_pDevice = a_devices[i];
					break;
				}
				
				// choose GPU or CPU device
				if ( u_gpu_dev == usize(-1) and EnumCmp( e_dev_type, cl_device::GPU ) )
					u_gpu_dev = i;

				if ( u_cpu_dev == usize(-1) and EnumCmp( e_dev_type, cl_device::CPU ) )
					u_cpu_dev = i;
			}

			if ( _pDevice == null )
			{
				const usize	i = ( u_gpu_dev != usize(-1) ? u_gpu_dev :
								( u_cpu_dev != usize(-1) ? u_cpu_dev : 0 ) );
				_pDevice =  a_devices[i];
			}
		}

		return true;
	}
	
/*
=================================================
	создание OpenCL контекста
=================================================
*/
	bool CLDevice::_CreateContext(const cl_context_properties *pProperties)
	{
		cl_int	i_cl_err = 0;
		bool	b_res;

		CL_CALL_B( _pContext = clCreateContext( pProperties, 1, &_pDevice, &_s_ErrorCallback, (void *)this, &i_cl_err ), b_res );
		
		if ( !b_res )
		{
			LOG( "Can't create OpenCL context.", e_log_msg::ERROR_3 );
			_pContext	= null;
			_pDevice	= null;
			_pPlatform	= null;
			return false;
		}
		return true;
	}

/*
=================================================
	удаление OpenCL контекста
=================================================
*/
	void CLDevice::Destroy()
	{
		if ( _pContext != null )
		{
			cl_int	i_cl_err = 0;
			bool	res;

			CL_CALL_B( i_cl_err = clReleaseContext( _pContext ), res );
			_pContext = null;
		}

		_pPlatform		= null;
		_pDevice		= null;
		_uTotalMemory	= 0;
		_bSupportNative	= false;
		_bSharedWithGL	= false;
		_bSharedWithDX	= false;

		CL_Delete();
	}

/*
=================================================
	записать информацию о OpenCL в строку
=================================================
*/
	void CLDevice::GetOpenCLInfo(string &sInfo) const
	{
		if ( _pDevice == null )
			return;

		const uint	u_max_size = 512;	
		string		a_info;
		a_info.Reserve( u_max_size );
		
		sInfo << "---------------\nOpenCL info\n---------------";

		a_info[0] = '\0';
		clGetPlatformInfo( _pPlatform, CL_PLATFORM_NAME, u_max_size, a_info.ptr(), null );
		sInfo << "\nPlatform:  " << a_info.ptr();
		
		a_info[0] = '\0';
		clGetPlatformInfo( _pPlatform, CL_PLATFORM_VERSION, u_max_size, a_info.ptr(), null );
		sInfo << "\nVersion:   " << a_info.ptr();
		
		a_info[0] = '\0';
		clGetDeviceInfo( _pDevice, CL_DEVICE_NAME, u_max_size, a_info.ptr(), null );
		sInfo << "\nDevice:    " << a_info.ptr();
		
		a_info[0] = '\0';
		clGetDeviceInfo( _pDevice, CL_DRIVER_VERSION, u_max_size, a_info.ptr(), null );
		sInfo << "\nDriver:    " << a_info.ptr();
		
		a_info[0] = '\0';
		clGetDeviceInfo( _pDevice, CL_DEVICE_PROFILE, u_max_size, a_info.ptr(), null );
		sInfo << "\nProfile:   " << a_info.ptr();
		sInfo.EraseFromBack( 8 );	// length of "_PROFILE"
		
		cl_device::type	e_type = cl_device::UNKNOWN;
		clGetDeviceInfo( _pDevice, CL_DEVICE_TYPE, sizeof(e_type), &e_type, null );
		
		sInfo << "\nType:      ";
		if ( e_type == cl_device::UNKNOWN )				{ sInfo << "unknown";	return; }
		if ( EnumCmp( e_type, cl_device::CPU ) )			sInfo << "CPU, ";
		if ( EnumCmp( e_type, cl_device::GPU ) )			sInfo << "GPU, ";
		if ( EnumCmp( e_type, cl_device::ACCELERATOR ) )	sInfo << "Accelerator, ";
		if ( EnumCmp( e_type, cl_device::DEFAULT ) )		sInfo << "Default, ";
		
		sInfo.EraseFromBack( 2 );	// lenth of ","
		sInfo << '\n';
	}
	
/*
=================================================
	возвращает параметры девайса
=================================================
*/
	void CLDevice::GetCLParams(uint &uFreq, uint &uComputeUnits, uint64 &uLocalMem, uint64 &uWorkGroup,
								uint64 &uConstBuf, uint64 &uGlobalMem, uint64 &uGlobalCache) const
	{
		cl_uint		u_value32	= 0;
		cl_ulong	u_value64	= 0;
		size_t		u_size		= 0;
		cl_int		i_cl_err	= 0;

		u_value32 = 0;
		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(cl_uint), &u_value32, null ) );
		uFreq = (uint)u_value32;

		u_value32 = 0;
		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(cl_uint), &u_value32, null ) );
		uComputeUnits = (uint)u_value32;

		u_value64 = 0;
		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_LOCAL_MEM_SIZE, sizeof(cl_ulong), &u_value64, null ) );
		uLocalMem = (uint64)u_value64;

		u_size = 0;
		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(size_t), &u_size, null ) );
		uWorkGroup = (uint64)u_size;

		u_value64 = 0;
		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_GLOBAL_MEM_CACHE_SIZE, sizeof(cl_ulong), &u_value64, null ) );
		uGlobalCache = (uint64)u_value64;

		u_value64 = 0;
		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(cl_ulong), &u_value64, null ) );
		uGlobalMem = (uint64)u_value64;
		
		u_value64 = 0;
		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, sizeof(cl_ulong), &u_value64, null ) );
		uConstBuf = (uint64)u_value64;
	}
	
/*
=================================================
	записать параметров девайса в строку
=================================================
*/
	void CLDevice::GetOpenCLParams(string &sParams) const
	{
		uint	u_freq			= 0;
		uint	u_compute_units	= 0;
		uint64	u_local_mem		= 0;
		uint64	u_work_group	= 0;
		uint64	u_global_cache	= 0;
		uint64	u_global_mem	= 0;
		uint64	u_const_buf		= 0;

		GetCLParams( u_freq, u_compute_units, u_local_mem, u_work_group, u_const_buf, u_global_mem, u_global_cache );

		sParams << "---------------\nOpenCL params\n---------------"
				<< "\nFrequency:     " << u_freq << " MHz"
				<< "\nCompute Units: " << u_compute_units
				<< "\nLocal Mem:     " << (u_local_mem>>10) << " Kb"
				<< "\nWork Group:    " << u_work_group
				<< "\nGlobal Cache:  " << (u_global_cache>>10) << " Kb"		// L1 cache
				<< "\nGlobal Mem:    " << (u_global_mem>>20) << " Mb"
				<< "\nConst Buffer:  " << (u_const_buf>>10) << " Kb"
				<< '\n';
	}
	
/*
=================================================
	возвращает максимальный размер изображений
=================================================
*/
	void CLDevice::GetMaxSizeOfImage2D(uvec2 &vMaxSize) const
	{
		size_t	u_size	 = 0;
		cl_int	i_cl_err = 0;

		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_IMAGE2D_MAX_WIDTH, 0, null, &u_size ) );
		vMaxSize.x = (uint) u_size;

		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_IMAGE2D_MAX_HEIGHT, 0, null, &u_size ) );
		vMaxSize.y = (uint) u_size;
	}

	void CLDevice::GetMaxSizeOfImage3D(uvec3 &vMaxSize) const
	{
		size_t	u_size	 = 0;
		cl_int	i_cl_err = 0;

		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_IMAGE3D_MAX_WIDTH, 0, null, &u_size ) );
		vMaxSize.x = (uint) u_size;

		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_IMAGE3D_MAX_HEIGHT, 0, null, &u_size ) );
		vMaxSize.y = (uint) u_size;

		CL_CALL( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_IMAGE3D_MAX_DEPTH, 0, null, &u_size ) );
		vMaxSize.z = (uint) u_size;
	}

/*
=================================================
	проверяет поддерживается ли разширение
=================================================
*/
	bool CLDevice::IsExtSupported(uni_c_string pExtName) const
	{
		CHECK_ERR( not pExtName.Empty() );
		CHECK_ERR( _pDevice != null );

		size_t	u_size		= 0;
		cl_int	i_cl_err	= 0;
		
		CL_CALL_R( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_EXTENSIONS, 0, null, &u_size ) );

		string	s_buf;
		s_buf.Reserve( u_size + 2 );

		CL_CALL_R( i_cl_err = clGetDeviceInfo( _pDevice, CL_DEVICE_EXTENSIONS, u_size, s_buf.ptr(), null ) );

		s_buf.SetLength( u_size );

		usize	u_pos = 0;
		return s_buf.Find( pExtName, u_pos, 0 );
	}

/*
=================================================
	функция обработки ошибок
=================================================
*/
	void CLDevice::_ErrorCallback(uni_c_string errinfo, const void *private_info, size_t cb) const
	{
		LOG( (string("OpenCL error: ") << errinfo << ", cb: " << cb << '.').cstr(), e_log_msg::ERROR_3 );
	}

	void CL_CALLBACK CLDevice::_s_ErrorCallback(const char *errinfo, const void *private_info, size_t cb, void *user_data)
	{
		((CLDevice *)user_data)->_ErrorCallback( errinfo, private_info, cb );
	}

//-------------------------------------------------------------------

}	// CLShell
}	// UX_STL